/*
*  SkySoundEngine.cpp
*  
*
*  Created by Alexander Trutchenko on 11/02/12.
*  Copyright 2012 Free Team. All rights reserved.
*
*/

#include "SkySoundEngine.h"

#include "SkyOpenALContext.h"
#include "SkyOpenALInclude.h"
#include "SkyBufferManager.h"
#include "SkyAssert.h"
#include "SkyTypes.h"

namespace sky {

////////////////////////////////////////////////////////
//
/** SkySoundEngine
*/
////////////////////////////////////////////////////////

SkySoundEngine* SkySoundEngine::_instance = NULL;

SkySoundEngine* SkySoundEngine::sharedEngine()
{
	if (_instance == NULL)
	{
		_instance = new SkySoundEngine();  
	}
	return _instance;
}

void SkySoundEngine::pvt_remove()
{
    if (NULL != _instance)
    {
        delete _instance;
        _instance = NULL;
    }
};

SkySoundEngine::SkySoundEngine()
: _currentContext(NULL)
, _magorVersion(0)
, _minorVersion(0)
, _outputDevice(NULL)
{
	_bufferManager = new SkyBufferManager();
}

SkySoundEngine::~SkySoundEngine()
{
	clearAllContexts();
	for (Keys::const_iterator it = _buffers.begin(); it != _buffers.end(); ++it)
		_bufferManager->unloadFileFromBuffer(it->c_str());
	for (Keys::const_iterator it = _compressedFiles.begin(); it != _compressedFiles.end(); ++it)
		_bufferManager->unloadCompressedFile(it->c_str());
	delete _bufferManager; _bufferManager = NULL;
}

////////////////////////////////////////////////////////
// work with output devices
bool SkySoundEngine::openOutputDevice(const ALCchar *deviceSpecifier)
{
	SkyAssert(_outputDevice == NULL, "SkySoundEngine::openOutputDevice(): Output device list must be closed before opening!");
	_outputDevice = alcOpenDevice(deviceSpecifier);
	if (_outputDevice == NULL)
		return false;

	alcGetIntegerv(NULL, ALC_MAJOR_VERSION, sizeof(_magorVersion), &_magorVersion);
	alcGetIntegerv(NULL, ALC_MINOR_VERSION, sizeof(_minorVersion), &_minorVersion);

	return true;
}

bool SkySoundEngine::closeOutputdevice()
{
	clearAllContexts();
	_buffers.clear();
	_bufferManager->clearAllBuffers();

	ALCboolean result = alcCloseDevice (_outputDevice);
	SkyAssert(result, "SkySoundEngine::closeOutputdevice(): Can't close current device!");
	return (result != 0);
}

void SkySoundEngine::availableOutputDevices(std::list<std::string>& deviceList)
{
	const char* res_c = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
	std::string res = res_c;
	if (res.length() != 0)
		deviceList.push_back(res);
	unsigned int nCursor = res.length() + 1;
	while (res_c[nCursor] != 0)
	{
		res = &(res_c[nCursor]);
		if (res.length() != 0)
			deviceList.push_back(res);
		nCursor += res.length() + 1;
	}
}

////////////////////////////////////////////////////////
// work with contexts
bool SkySoundEngine::addContext(SkyOpenALContext* context)
{
	SkyAssert(context != NULL, "SkySoundEngine::createContext(): new context must not be NULL!");
	SkyAssert(_outputDevice != NULL, "SkySoundEngine::createContext(): Output device must not be closed!");
	
	if (!context->init(_bufferManager,
				  _outputDevice,
				  _magorVersion,
				  _minorVersion))
	{
		delete context;
		return false;
	}

	_contexts.push_back(context);
	
	return true;
}

bool SkySoundEngine::removeContext(SkyOpenALContext* context)
{
	for (Contexts::const_iterator it = _contexts.begin(); it != _contexts.end(); ++it)
		if (*it == context)
		{
			if (_currentContext == context)
				setCurrentContext(NULL);
			delete context;
			_contexts.erase(it);
			return true;
		}
	return false;
}

void SkySoundEngine::clearAllContexts()
{
	setCurrentContext(NULL);
	for (Contexts::const_iterator it = _contexts.begin(); it != _contexts.end(); ++it)
		delete *it;
	_contexts.clear();
}

void SkySoundEngine::setCurrentContext(SkyOpenALContext* context)
{
	if (_currentContext == context)
		return;

	//Default global settings
	for (Contexts::const_iterator it = _contexts.begin(); it != _contexts.end(); ++it)
		if (*it == context)
		{
			if (_currentContext != NULL)
			{
				_currentContext->setIsCurrent(false);
				_bufferManager->onClearCurrentContext();
			}
			_currentContext = context;
			_currentContext->setIsCurrent(true);
			_bufferManager->onSetCurrentContext();

			return;
		}
   if (context == NULL)
   {
	   _currentContext->setIsCurrent(false);
	   _currentContext = context;
	   _bufferManager->onClearCurrentContext();
	   return;
   }
	   
   SkyAssert(false, "SkySoundEngine::setCurrentContext: context is not exists!"); 
}

////////////////////////////////////////////////////////
// Timer
void SkySoundEngine::update(float dt)
{
	if (_currentContext != NULL)
		_currentContext->update(dt);
}

////////////////////////////////////////////////////////
// Preload/unload global effects and files 
bool SkySoundEngine::preloadCompressedFile(const char* file)
{
	Keys::const_iterator it = _compressedFiles.find(file);
	if (it == _compressedFiles.end())
	{
		bool result = _bufferManager->preloadCompressedFile(file);
		if (result)
			_compressedFiles.insert(file);
		return result;
	}
	return true;
}

void SkySoundEngine::unloadCompressedFile(const char* file)
{
	Keys::const_iterator it = _compressedFiles.find(file);
	if (it != _compressedFiles.end())
	{
		_bufferManager->unloadCompressedFile(file);
		_compressedFiles.erase(file);
	}
}
bool SkySoundEngine::preloadFileToBuffer(const char* file)
{
	Keys::const_iterator it = _buffers.find(file);
	if (it == _buffers.end())
	{
		bool result = _bufferManager->preloadFileToBuffer(file);
		if (!result)
			return false;
		_buffers.insert(file);
	}
	return true;
}
void SkySoundEngine::unloadFileFromBuffer(const char* file)
{
	Keys::const_iterator it = _buffers.find(file);
	if (it != _buffers.end())
	{
		_bufferManager->unloadFileFromBuffer(file);
		_buffers.erase(*it);
	}
}

////////////////////////////////////////////////////////
// Global settings

const char* SkySoundEngine::stringInfo(ALenum infoType)
{
	return alGetString(infoType);
}

////////////////////////////////////////////////////////
// Support alc functions

bool SkySoundEngine::alcIsExtensionAvailable_output(const char *extName) const
{
	return (alcIsExtensionPresent(_outputDevice, extName) == AL_TRUE);
}

void* SkySoundEngine::alcFunctionEntryAddress_output(const char *funcName) const
{
	return alcGetProcAddress(_outputDevice, funcName);
}

ALCenum SkySoundEngine::alcLastError_output() const
{
	return alcGetError(_outputDevice);
}

ALCenum SkySoundEngine::alcEnumValue_output(const char* enumName) const
{
	return alcGetEnumValue(_outputDevice, enumName);
}

const char* SkySoundEngine::alcStringValue_output(ALCenum token) const
{
	return alcGetString(_outputDevice, token);
}

int SkySoundEngine::alcIntegerValue_output(ALCenum token) const //Only for ALC_CAPTURE_SAMPLES, ALC_MAJOR_VERSION, ALC_MINOR_VERSION
{
	ALCint dest = 0;
	alcGetIntegerv(_outputDevice, token, sizeof(dest), &dest);
	return dest;
}



} // namespace sky
